# -*- coding: cp1252 -*-
#"Uso:
#	python perceptron [parametros] entradas control salidas

import numpy
import random
import sys
import getopt
import math

Char = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']

def num(N):
	return int(N[0,0]*(2**4) + N[0,1]*(2**3) + N[0,2]*(2**2) + N[0,3]*2 + N[0,4])

def printError(exp,res):
	C = []
	print "error:", error(exp.T,res.T)
	for i in range(26):
		C.append(num(res[i]))
	return C

def hardlim(n):
	if n<0: return 0
	else: return 1

def evaluar(W,b,inp):

	# supongo que inp tiene dimensiones: nEntradas x nPatrones
	nEntradas=inp.shape[0]
	nPatrones=inp.shape[1]
	nSalidas=W.shape[0]

#	print nEntradas, nPatrones, nSalidas

	h = numpy.mat(numpy.zeros((nSalidas,nPatrones)))
	O = numpy.mat(numpy.zeros((nSalidas,nPatrones)))

	for m in range(nPatrones):

		# Asigno a la columna m de h el valor que le corresponde (las salidas para ese patron)
		h[:,m] = W*inp[:,m] + b[:]

		# aplico la funcion a cada salida para cada patron (a cada elemento de la matriz h)
		for s in range(nSalidas):
			O[s,m] = hardlim(h[s,m])

	return O

def error(exp,outp):

	E = 0
	delta = exp - outp

	# esto da la normal del error al cuadrado
	for m in range(outp.shape[1]):
		E = E + 0.5*float(delta[:,m].T*delta[:,m])

	return E

def entrenar(inp,outp,cotaInfErr,max_iter,learning_rate,seed):

	# supongo que inputs tiene dimensiones: nEntradas x nPatrones
	# supongo que la salida tiene dimensiones: nSalidas x nPatrones

	nEntradas=inp.shape[0]
	nPatrones=inp.shape[1]
	nSalidas=outp.shape[0]

	numpy.random.seed(seed);
	W=numpy.mat(numpy.random.rand(nSalidas,nEntradas))
	b=numpy.mat(numpy.ones((nSalidas,1)))

	E = cotaInfErr + 1
	i=0

	min_E = float('inf')
	min_W = W.__copy__()
	min_b = b.__copy__()
	min_i = i

	while (E>cotaInfErr and i<max_iter):

		O = evaluar(W,b,inp)
		delta = outp - O
		E = error(outp,O)

		# si encuentro un mejor W (con menor error) lo actualizo
		if E<min_E:
			min_E = E
			min_W = W.__copy__()
			min_b = b.__copy__()
			min_i = i

		# actualizo los pesos de la matriz
		for m in range(nPatrones):
			for s in range(nSalidas):
				for e in range(nEntradas):
					W[s,e] = W[s,e] + learning_rate * delta[s,m] * inp[e,m]
				b[s] = b[s] + learning_rate * delta[s,m] * -1

		i=i+1
	# end while

	return [min_W,min_b,min_E,min_i]

def test():

	rep = 10;

	f_in = open("Letras/letras.in",'r')
	M_in = (numpy.matrix(f_in.read())).T
	f_in.close()
	f_out = open("Letras/letras.out",'r')
	M_out = (numpy.matrix(f_out.read())).T
	f_out.close()
	f = open("res/lrs.out",'w')
	lrs = [ float(i)/10 for i in range(1,30) ]
	for lr in lrs:
		print lr
		mE  = 0;
		mi = 0;
		absE = float('inf');
		absi = 0;
		for s in range(rep):
			print "	", s
			[W,b,E,i] = entrenar(M_in,M_out,0,500,lr,s)
			mE = mE + E;
			mi = mi + i;
			if E < absE:
				absE = E
				absi = i
		f.write( str(lr)+" "+str(mE/rep)+" "+str(float(mi)/rep)+" "+str(absE)+" "+str(absi)+"\n" )
	f.close()

def correr():

	f_in = open("Letras/letras_ruido2.in",'r')
	M_in = (numpy.matrix(f_in.read())).T
	f_in.close()

	f_out = open("Letras/letras.out",'r')
	M_out = (numpy.matrix(f_out.read())).T
	f_out.close()

	f_test = open("Letras/letras.in",'r')
	M_test0 = (numpy.matrix(f_test.read())).T
	f_test.close()

	f_test = open("Letras/letras_ruido1.in",'r')
	M_test1 = (numpy.matrix(f_test.read())).T
	f_test.close()

	f_test = open("Letras/letras_ruido2.in",'r')
	M_test2 = (numpy.matrix(f_test.read())).T
	f_test.close()

	f_test = open("Letras/letras_ruido3.in",'r')
	M_test3 = (numpy.matrix(f_test.read())).T
	f_test.close()

	f_test = open("Letras/letras_ruido4.in",'r')
	M_test4 = (numpy.matrix(f_test.read())).T
	f_test.close()

	f_test = open("Letras/letras_ruido5.in",'r')
	M_test5 = (numpy.matrix(f_test.read())).T
	f_test.close()

	[W,b,E,i] = entrenar(M_in,M_out,0,500,.1,1)

	Cs = []

	Cs.append(range(26))

	O = evaluar(W,b,M_test0)
	Cs.append(printError(M_out.T,O.T))

	O = evaluar(W,b,M_test1)
	Cs.append(printError(M_out.T,O.T))

	O = evaluar(W,b,M_test2)
	Cs.append(printError(M_out.T,O.T))

	O = evaluar(W,b,M_test3)
	Cs.append(printError(M_out.T,O.T))

	O = evaluar(W,b,M_test4)
	Cs.append(printError(M_out.T,O.T))

	O = evaluar(W,b,M_test5)
	Cs.append(printError(M_out.T,O.T))

#	print transpose(Cs)
#	for i in range(26):
#		print Cs[0][i], Cs[1][i], Cs[2][i], Cs[3][i], Cs[4][i], Cs[5][i]

# Hay que ver:
# 	* Correr con varios learning rates distintos 0<lr<=1 para ver como cambia el comportamiento y elegir uno adecuado  (el mejor?).
#	  tal vez habria que hacerlo con la misma matriz de pesos inicial. poner el mismo seed?
# 	* Analizar los resultados para conseguir una cota de iteraciónes razonable
# 	* Cual es una buena cota minima para el error?
# 	* numpy no esta levantando bien las matrices en el formato de los archivos de entrada con ruido. corregir
#	* Habría que definir conjuntos de validación y testeo. entrenar sin ruido y testear con? entrenar con ruido y testear sin?
#	* Se puede estudiar la diferencia del comportamiento al actualizar los pesos sincronica o asincronicamente

correr()
